// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.

#include "RPG.h"
#include "RPGCharacter.h"
#include "RPGWorldSettings.h"
#include "Engine.h"
#include "UnrealNetwork.h"
#include "DataTableFunctions.h"
#include "RPGHUD.h"

//////////////////////////////////////////////////////////////////////////
// ARPGCharacter

ARPGCharacter::ARPGCharacter(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	// set our turn rates for input
	BaseTurnRate = 45.f;
	BaseLookUpRate = 45.f;

	// Don't rotate when the controller rotates. Let that just affect the camera.
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// Configure character movement
	GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...	
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
	GetCharacterMovement()->JumpZVelocity = 600.f;
	GetCharacterMovement()->AirControl = 0.2f;

	// Create a camera boom (pulls in towards the player if there is a collision)
	CameraBoom = ObjectInitializer.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("CameraBoom"));
	CameraBoom->AttachTo(RootComponent);
	CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character	
	CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

	// Create a follow camera
	FollowCamera = ObjectInitializer.CreateDefaultSubobject<UCameraComponent>(this, TEXT("FollowCamera"));
	FollowCamera->AttachTo(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
	FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm

	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
	// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)

	CollectSphere = ObjectInitializer.CreateDefaultSubobject<USphereComponent>(this, TEXT("CollectSphere"));
	CollectSphere->AttachTo(RootComponent);
	CollectSphere->SetSphereRadius(800.f);

	bHasNewFocus = true;

	PrimaryActorTick.bCanEverTick = true;
	PrimaryActorTick.bStartWithTickEnabled = true;
	PrimaryActorTick.bAllowTickOnDedicatedServer = true;
}

void ARPGCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(ARPGCharacter, Inventario);
}

struct FDistanceComparer
{
	AActor* Reference;

	FDistanceComparer(AActor* Reference)
	{
		this->Reference = Reference;
	}

	bool operator() (const AActor& A, const AActor& B) const
	{
		float da = FVector::Dist(Reference->GetActorLocation(), A.GetActorLocation());
		float db = FVector::Dist(Reference->GetActorLocation(), B.GetActorLocation());

		return da < db;
	}
};

AUsableActor* ARPGCharacter::GetUsableInRange()
{
	TArray<AActor*> overlaps;
	CollectSphere->GetOverlappingActors(overlaps, APickupActor::StaticClass());

	if (overlaps.Num() > 0)
	{
		FDistanceComparer pred(this);
		overlaps.Heapify(pred);
		return Cast<AUsableActor>(overlaps.HeapTop());
	}
	else
	{
		return NULL;
	}
}

void ARPGCharacter::BeginPlay()
{
	Super::BeginPlay();

	APlayerController* controller = Cast<APlayerController>(Controller);

	if (!controller)
	{
		return;
	}

	ARPGHUD* hud = Cast<ARPGHUD>(controller->GetHUD());

	if (!hud)
	{
		return;
	}

	UWorld* world = GetWorld();

	if (!world)
	{
		return;
	}

	FActorSpawnParameters parameters;
	parameters.Owner = this;
	parameters.Instigator = Instigator;

	Inventario = world->SpawnActor<ARPGInventario>(parameters);
	Inventario->Reconstruir(hud->Slots());

	AttachMeshes();

	Inventario->ItemAdicionado.AddDynamic(hud, &ARPGHUD::ItemAdicionado);
	Inventario->ItemRemovido.AddDynamic(hud, &ARPGHUD::ItemRemovido);
	Inventario->ItemEquipado.AddDynamic(hud, &ARPGHUD::ItemEquipado);
	Inventario->ItemDesequipado.AddDynamic(hud, &ARPGHUD::ItemDesequipado);
}

void ARPGCharacter::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	if (Controller && Controller->IsLocalController())
	{
		AUsableActor* usable = GetUsableInRange();

		if (FocusedUsableActor != usable)
		{
			if (FocusedUsableActor)
			{
				FocusedUsableActor->EndFocus();
			}

			bHasNewFocus = true;
		}

		FocusedUsableActor = usable;

		if (usable)
		{
			if (bHasNewFocus)
			{
				usable->BeginFocus();
				bHasNewFocus = false;
			}
		}
	}
}

void ARPGCharacter::Use_Implementation()
{
	AUsableActor* usable = GetUsableInRange();
	if (usable) {
		usable->OnUsed(this);
	}
}

bool ARPGCharacter::Use_Validate()
{
	return true;
}

ARPGInventario* ARPGCharacter::GetInventario() const
{
	return Inventario;
}

void ARPGCharacter::ServerPegarItem_Implementation(int32 ItemID)
{
	Inventario->AdicionarItem(ItemID);
}

bool ARPGCharacter::ServerPegarItem_Validate(int32 ItemID)
{
	return true;
}

void ARPGCharacter::ServerSoltarItem_Implementation(int32 ItemID)
{
	const FVector loc = GetActorLocation();
	const FRotator rot = GetActorRotation();
	const FVector spawnLoc = loc + FVector(0, 0, BaseEyeHeight) + rot.Vector() * 100.f;

	ARPGWorldSettings* ws = Cast<ARPGWorldSettings>(GetWorld()->GetWorldSettings());

	FActorSpawnParameters params;
	APickupActor* t = ws->SpawnClass.GetDefaultObject();
	t->ItemID = ItemID;
	params.Template = t;

	APickupActor* spawned = Cast<APickupActor>(GetWorld()->SpawnActor(ws->SpawnClass, &spawnLoc, &rot, params));

	Inventario->RemoverItem(ItemID);
}

bool ARPGCharacter::ServerSoltarItem_Validate(int32 ItemID)
{
	return true;
}

void ARPGCharacter::MostrarInventario()
{
	APlayerController* player_controller = Cast<APlayerController>(Controller);

	if (!player_controller)
	{
		return;
	}

	ARPGHUD* hud = Cast<ARPGHUD>(player_controller->GetHUD());

	if (!hud)
	{
		return;
	}

	hud->SetVisibilidade(!hud->Visibilidade);

	player_controller->bShowMouseCursor = hud->Visibilidade;
	player_controller->bEnableClickEvents = hud->Visibilidade;
	player_controller->SetIgnoreLookInput(hud->Visibilidade);
}

//////////////////////////////////////////////////////////////////////////
// Input

void ARPGCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	// Set up gameplay key bindings
	check(InputComponent);
	InputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
	InputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

	InputComponent->BindAction("Use", IE_Pressed, this, &ARPGCharacter::Use);
	InputComponent->BindAction("Inventario", IE_Pressed, this, &ARPGCharacter::MostrarInventario);

	InputComponent->BindAxis("MoveForward", this, &ARPGCharacter::MoveForward);
	InputComponent->BindAxis("MoveRight", this, &ARPGCharacter::MoveRight);

	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
	// "turn" handles devices that provide an absolute delta, such as a mouse.
	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
	InputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
	InputComponent->BindAxis("TurnRate", this, &ARPGCharacter::TurnAtRate);
	InputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
	InputComponent->BindAxis("LookUpRate", this, &ARPGCharacter::LookUpAtRate);

	// handle touch devices
	InputComponent->BindTouch(IE_Pressed, this, &ARPGCharacter::TouchStarted);
	InputComponent->BindTouch(IE_Released, this, &ARPGCharacter::TouchStopped);
}


void ARPGCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
{
	// jump, but only on the first touch
	if (FingerIndex == ETouchIndex::Touch1)
	{
		Jump();
	}
}

void ARPGCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
{
	if (FingerIndex == ETouchIndex::Touch1)
	{
		StopJumping();
	}
}

void ARPGCharacter::TurnAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void ARPGCharacter::LookUpAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

void ARPGCharacter::MoveForward(float Value)
{
	if ((Controller != NULL) && (Value != 0.0f))
	{
		// find out which way is forward
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);

		// get forward vector
		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		AddMovementInput(Direction, Value);
	}
}

void ARPGCharacter::MoveRight(float Value)
{
	if ( (Controller != NULL) && (Value != 0.0f) )
	{
		// find out which way is right
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);
	
		// get right vector 
		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// add movement in that direction
		AddMovementInput(Direction, Value);
	}
}
